﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using JasLib.Extensions;
using JasLib.Interop.Win32;
using System.Windows.Threading;
using JasLib.Diagnostics;

namespace JasLib
{
    /******************************************************************************************/
    public class DoNothingDisposable : IDisposable
    {
        public void Dispose()
        {
            return;
        }
    }

    /******************************************************************************************/
    public abstract class FinalizedDisposableObject : IDisposable
    {
        ~FinalizedDisposableObject()
        {
            if (!_bDisposed)
                Dispose(true);
        }

        private volatile bool _bDisposed = false;
        protected bool Disposed
        {
            get { return _bDisposed; }
        }

        protected void EnforceNotDisposed()
        {
            if (_bDisposed)
                throw new ObjectDisposedException("FinalizedDisposableObject", "Object was already disposed when the action was attempted.");
        }

        protected virtual void Dispose(bool bFinalizer)
        {
            throw new NotImplementedException();
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (!_bDisposed)
            {
                Dispose(false);
                GC.SuppressFinalize(this);
                _bDisposed = true;
            }

            return;
        }

        #endregion
    }

    /******************************************************************************************/
    public class LeakCheckingDispatcherObject : DispatcherObject, IDisposable
    {
#if DEBUG
        /// <summary>
        /// This is for resource leak detection; it tells us where the object was allocated.
        /// </summary>
        private string[] _constructorCallStack = null;

        public LeakCheckingDispatcherObject()
        {
            try
            {
                /// TODO: Use more finesse when null method information is encountered,
                /// so that we don't lose the whole stack trace.
                _constructorCallStack = new StackTrace()
                    .GetFrames()
                    .Select(f => f.GetMethod())
                    .Select(m => m.DeclaringType.FullName + "." + m.Name)
                    .ToArray();
            }
            catch
            {
                _constructorCallStack = new string[0];
            }
        }
#endif

        ~LeakCheckingDispatcherObject()
        {
#if DEBUG
            DialogResultType result = SHLWAPI.SHMessageBoxCheck(
                HWND.NULL,
                "You have not explicitly disposed an instance of {0} before it reached the finalizer! " +
                "This will likely cause a resource leak in release builds because the object can only be disposed from within the thread it was created on.\n\n" +
                "Break into the debugger?".FormatWith(GetType().Name),
                "Debug Assertion",
                MessageBoxFlags.MB_YESNO | MessageBoxFlags.MB_ICONEXCLAMATION,
                (int)DialogResultType.IDNO,
                "Finalizer assertion for " + GetType().FullName);
            if (result == DialogResultType.IDYES)
                DebugUtilities.Break();
#endif

            if (!_bDisposed)
                Dispose(true);
            return;
        }

        private bool _bDisposed = false;

        protected virtual void Dispose(bool bFinalizer)
        {
            throw new NotImplementedException();
        }

        #region IDisposable Members

        public void Dispose()
        {
            VerifyAccess();

            if (!_bDisposed)
            {
                Dispose(false);
                GC.SuppressFinalize(this);
                _bDisposed = true;
            }

            return;
        }

        #endregion
    }

    /******************************************************************************************/
    /// <summary>
    /// A disposable object that calls a provided delegate when disposed.
    /// If the disposal occurs on the finalizer thread, the delegate will be called from the thread pool.
    /// </summary>
    public sealed class DeadHand : FinalizedDisposableObject
    {
        private volatile bool _bPerformPostMortem = true; 
        private readonly Action<bool, object> _action = null;
        private readonly object _objContext = null;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="action">The first parameter of this delegate indicates whether it is being called from the finalizer.
        /// The second parameter is a context variable provided by the caller.</param>
        /// <param name="objContext">Caller-provided context variable which is passed to the delegate if/when it is called.</param>
        public DeadHand(Action<bool, object> action, object objContext = null)
        {
            _action = action;
            _objContext = objContext;
            return;
        }

        public object Context
        {
            get
            {
                return _objContext;
            }
        }

        /// <summary>
        /// This function stops the class from launching the action delegate during disposal.
        /// After calling this function, it is not necessary to call Dispose().
        /// </summary>
        public void Suppress()
        {
            _bPerformPostMortem = false;
            return;
        }

        protected override void Dispose(bool bFinalizer)
        {
            if (_bPerformPostMortem)
            {
                if (bFinalizer)
                    ThreadPool.QueueUserWorkItem((obj) => this._action(true, obj), _objContext);
                else
                    _action(false, _objContext);
            }
            return;
        }
    }
}
